package com.conversationboard.fulltext.index;

import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.CorruptIndexException;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;
import org.joda.time.DateTime;
import org.joda.time.MutableDateTime;

import com.conversationboard.config.Configuration;
import com.conversationboard.database.BatchQueries;
import com.conversationboard.database.Database;
import com.conversationboard.fulltext.DocumentType;
import com.conversationboard.fulltext.data.MessageData;
import com.conversationboard.fulltext.data.ThreadData;

public class ReindexDocuments {

        public void reindexDocuments() throws SQLException, IOException {

                this.reindexThreads();
                this.reindexMessages();
        }


        /**
         * Ensure that the query:
         *  (1) Gets the current time; assign to currentTime
         *  (2) Writes that as the time when the last indexing took place
         *  (3) Queries the database to only retrieve between the last indexing and what currentTime is set to
         *  
         *  @param pass in an empty list of ThreadData objects and this method will fill it
         *  @return the timestamp of the most recent thread when the sample was taken
         */

        private MutableDateTime getUpdatedThreads(List<ThreadData> threads) throws SQLException {

                Connection connection = null;

                try {

                        MutableDateTime maxDateTime = new MutableDateTime(1, 1, 1, 1, 1, 1, 1);

                        connection = Database.getConnection();
                        PreparedStatement statement = connection.prepareStatement(BatchQueries.GET_THREADS_NEEDING_REINDEXED);

                        statement.execute();

                        ResultSet resultSet = statement.getResultSet();

                        while (resultSet.next()) {

                                int boardId = resultSet.getInt("boardid");
                                int threadId = resultSet.getInt("threadid");
                                String title = resultSet.getString("title");
                                String displayName = resultSet.getString("displayname");
                                String loginId = resultSet.getString("loginid");
                                boolean deleted = resultSet.getBoolean("deleted");
                                Timestamp timestamp = resultSet.getTimestamp("timestamp");

                                DateTime time = new DateTime(timestamp);

                                /* Ensure we keep a record of the most recent new thread */

                                if (time.isAfter(maxDateTime)) {
                                        maxDateTime = time.toMutableDateTime();
                                }

                                ThreadData thread = new ThreadData(boardId, threadId, title, displayName, loginId, timestamp, deleted);
                                threads.add(thread);
                        }

                        resultSet.close();
                        statement.close();

                        return maxDateTime;

                } finally {
                        Database.close(connection);
                }
        }


        /**
         * Add all the threads into the Lucene index.
         * 
         * @param threads
         * @throws IOException
         */

        private void reindexThreads(List<ThreadData> threads) throws CorruptIndexException, IOException {

                if (threads.size() == 0) {
                        return;
                }

                File indexDirectory = new File(Configuration.getInstance().getThreadIndexPath());
                IndexWriter indexWriter = new IndexWriter(indexDirectory, new StandardAnalyzer());

                try {

                        Field id = new Field("id", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field boardId = new Field("boardId", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field title = new Field("text", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field name = new Field("displayName", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field login = new Field("loginId", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field date = new Field("date", "", Field.Store.YES, Field.Index.UN_TOKENIZED);
            			Field time = new Field("time", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field deleted = new Field("deleted", "", Field.Store.YES, Field.Index.UN_TOKENIZED);

                        SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd");
            			SimpleDateFormat timeFormatter = new SimpleDateFormat("HHmmss");

                        for (ThreadData thread : threads) {

                                Document document = new Document();

                                id.setValue(Identifier.generateId(thread.getBoardId(), thread.getThreadId()));
                                boardId.setValue(thread.getBoardId() + "");
                                title.setValue(thread.getTitle());
                                name.setValue(thread.getDisplayName());
                                login.setValue(thread.getLoginId());

                                if (thread.isDeleted()) {
                                        deleted.setValue("true");
                                } else {
                                        deleted.setValue("false");
                                }
                                
                				date.setValue(formatter.format(thread.getTimestamp()));
                				time.setValue(timeFormatter.format(thread.getTimestamp()));
                				
                                document.add(id);
                                document.add(boardId);
                                document.add(title);
                                document.add(name);
                                document.add(login);
                                document.add(date);
                				document.add(time);
                                document.add(deleted);

                                Term term = new Term("id", Identifier.generateId(thread.getBoardId(), thread.getThreadId()));
                                indexWriter.updateDocument(term, document);
                        }

                } finally {
                        indexWriter.close();
                }
        }


        /**
         * (1) Get all the threads to be indexed and retrieve the most recent timestamp
         * (2) Index the threads in Lucene
         * (3) Mark all these threads as indexed in the database
         */

        private void reindexThreads() throws SQLException, IOException {

                List<ThreadData> threads = new LinkedList<ThreadData>();

                MutableDateTime mostRecentThreadTimestamp = this.getUpdatedThreads(threads);
                this.reindexThreads(threads);
                this.markReindexed(mostRecentThreadTimestamp, DocumentType.Threads);
        }


        private MutableDateTime getUpdatedMessages(List<MessageData> messages) throws SQLException {

                Connection connection = null;

                try {

                        MutableDateTime maxDateTime = new MutableDateTime(1, 1, 1, 1, 1, 1, 1);

                        connection = Database.getConnection();
                        PreparedStatement statement = connection.prepareStatement(BatchQueries.GET_MESSAGES_NEEDING_REINDEXED);

                        statement.execute();

                        ResultSet resultSet = statement.getResultSet();

                        while (resultSet.next()) {

                                int boardId = resultSet.getInt("boardid");
                                int threadId = resultSet.getInt("threadid");
                                int messageId = resultSet.getInt("messageid");
                                String text = resultSet.getString("body");
                                String displayName = resultSet.getString("displayname");
                                String loginId = resultSet.getString("userid");
                                Timestamp timestamp = resultSet.getTimestamp("timestamp");
                                boolean deleted = resultSet.getBoolean("deleted");

                                DateTime time = new DateTime(timestamp);

                                /* Ensure we keep a record of the most recent new thread */

                                if (time.isAfter(maxDateTime)) {
                                        maxDateTime = time.toMutableDateTime();
                                }

                                MessageData message = new MessageData(boardId, threadId, messageId, text, loginId, displayName, timestamp, deleted);
                                messages.add(message);
                        }

                        resultSet.close();
                        statement.close();

                        return maxDateTime;

                } finally {
                        Database.close(connection);
                }

        }


        private void reindexMessages(List<MessageData> messages) throws CorruptIndexException, IOException {

                if (messages.size() == 0) {
                        return;
                }

                File indexDirectory = new File(Configuration.getInstance().getMessageIndexPath());
                IndexWriter indexWriter = new IndexWriter(indexDirectory, new StandardAnalyzer());

                try {
                        Field id = new Field("id", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field boardId = new Field("boardId", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field text = new Field("text", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field messageId = new Field("messageId", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field name = new Field("displayName", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field login = new Field("loginId", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field date = new Field("date", "", Field.Store.YES, Field.Index.UN_TOKENIZED);
            			Field time = new Field("time", "", Field.Store.YES, Field.Index.TOKENIZED);
                        Field deleted = new Field("deleted", "", Field.Store.YES, Field.Index.UN_TOKENIZED);

                        SimpleDateFormat formatter = new SimpleDateFormat("yyMMdd");
            			SimpleDateFormat timeFormatter = new SimpleDateFormat("HHmmss");

                        for (MessageData message : messages) {

                                Document document = new Document();

                                id.setValue(Identifier.generateId(message.getBoardId(), message.getThreadId(), message.getMessageId()));
                                boardId.setValue(message.getBoardId() + "");
                                text.setValue(message.getText());
                                messageId.setValue(message.getMessageId() + "");
                                name.setValue(message.getDisplayName());
                                login.setValue(message.getLoginId());
                                
                                if (message.isDeleted()) {
                                        deleted.setValue("true");
                                } else {
                                        deleted.setValue("false");
                                }

                				date.setValue(formatter.format(message.getTimestamp()));
                				time.setValue(timeFormatter.format(message.getTimestamp()));

                                document.add(id);
                                document.add(boardId);
                                document.add(text);
                                document.add(messageId);
                                document.add(name);
                                document.add(login);
                                document.add(date);
                				document.add(time);
                                document.add(deleted);

                                Term term = new Term("id", Identifier.generateId(message.getBoardId(), message.getThreadId(), message.getMessageId()));
                                indexWriter.updateDocument(term, document);
                                
                        }

                } finally {
                        indexWriter.close();
                }

        }


        private void markReindexed(MutableDateTime mostRecentDocumentTimestamp, DocumentType documentType) throws SQLException {

                Connection connection = null;

                try {

                        String query = "";

                        if (documentType == DocumentType.Threads) {
                                query = BatchQueries.MARK_THREADS_REINDEXED;
                        } else if (documentType == DocumentType.Messages) {
                                query = BatchQueries.MARK_MESSAGES_REINDEXED;
                        } else {
                                throw new IllegalArgumentException("Pass in either DocumentType.Threads or DocumentType.Messages");
                        }

                        connection = Database.getConnection();

                        PreparedStatement statement = connection.prepareStatement(query);
                        statement.setTimestamp(1, new Timestamp(mostRecentDocumentTimestamp.toDate().getTime()));

                        statement.executeUpdate();

                        statement.close();

                } finally {
                        Database.close(connection);
                }

        }


        private void reindexMessages() throws SQLException, CorruptIndexException, IOException {

                List<MessageData> messages = new LinkedList<MessageData>();

                MutableDateTime mostRecentMessageTimestamp = this.getUpdatedMessages(messages);
                this.reindexMessages(messages);
                this.markReindexed(mostRecentMessageTimestamp, DocumentType.Messages);

        }

}
